home *** CD-ROM | disk | FTP | other *** search
/ Amiga Plus 2004 #11 / Amiga Plus CD - 2004 - No. 11.iso / AmiSoft / Comm / mail / SpamFryer.lha / SpamFryer / SpamFryer.rexx < prev    next >
OS/2 REXX Batch file  |  2004-08-20  |  21KB  |  655 lines

  1. /* sys:rexxc/rx
  2.  *
  3.  *  Checks through emails on a POP3 host and purges the unwanted ones.
  4.  *
  5.  *  Shows a summary of waiting messages and deletes any with html right
  6.  *  the start, or subjects or a recipient address known to be bogus.
  7.  *  Includes special sifting for recent Windoze .PIF worms and mails
  8.  *  flagged by SpamAssasin on the server. Easy to extend for similar
  9.  *  cases, but catches 80..90 per cent of spams without special code.
  10.  *
  11.  *  SpamFryer by <amiga (at) studio (dot) co (dot) uk>
  12.  *  Improved by <allan (dot) tnt (at) post (dot) tele (dot) dk>
  13.  *  Inspired by ChkMail (on Aminet) written in 1994 by
  14.  *  Matt White <whitem (at) duke (dot) usask (dot) ca>
  15.  *
  16.  *  $VER: SpamFryer.rexx 3:9 (2004-8-19) by Simon N Goodwin $
  17.  *
  18.  *  SpamFryer.text lists changes since November 2003 Aminet release.
  19.  */
  20.  
  21.    Account. = '' /* resets the array */
  22.    count.1 = 0
  23.    count.2 = 0
  24.  
  25.  
  26.  
  27.  
  28.    /*** CONFIGURATION BLOCK **/
  29.  
  30.    /* Account details can be in the file SpamFryer.accounts, or here in the following format: */       
  31.    /* Account.1.1 = "new.mail.org" */ /* IP number e.g. 194.5.6.7 or domain e.g. mail.isp.net */
  32.    /* Account.1.2 = "username1"    */ /* Your user name - typically your ISP account name     */
  33.    /* Account.1.3 = "password1"    */ /* The login password you use for email - confidential! */   
  34.    /* Continue for Account.2.1, Account 2.2 and Account.2.3 etc if you have several accounts. */
  35.    
  36.    keepList    = 'SpamFryer.keepList'  /* The path and name of your file of details of mails you want to keep */
  37.    loseList    = 'SpamFryer.loseList'  /* The path and name of your file of details of mails you want to lose */
  38.    accountFile = 'SpamFryer.accounts'  /* The file of login details for your POP3 accounts (if not set above) */
  39.  
  40.    logFile     = 'T:SpamFryer.log'     /* Specifies the log file this program generates */
  41.    screenName  = '*'                   /* log window public screen name, '' for no window or '*' for the default */
  42.    lineMax     = 60                    /* The maximum width of subject line characters reported to console */
  43.    noDigits    = 0                     /* Non-zero to lose all mails with digits in their To: address */
  44.  
  45.    /**************************** END OF CONFIGURATION BLOCK ****************************/
  46.  
  47.  
  48.  
  49.  
  50.    OPTIONS results /* make Arexx return results from various functions */
  51.  
  52.       
  53.    PARSE ARG quiet .
  54.    IF (UPPER(quiet) == "VERBOSE") THEN
  55.       verbose = 1
  56.    ELSE
  57.       verbose = 0
  58.  
  59.    IF screenName ~='' THEN DO
  60.      CALL close STDOUT
  61.      CALL open(STDOUT, "con:48/48/590/150/SpamFryer3 Log  --  Click CLOSE gadget when finished/CLOSE/WAIT/SCREEN " screenName, w)
  62.    END
  63.  
  64.    IF logFile ~='' THEN
  65.       IF ~open(log,logFile,'A') THEN
  66.          IF ~open(log,logFile,'W') THEN
  67.             logFile = ''
  68.  
  69.    CALL Check_Accounts
  70.  
  71.    keep    = 1 /* Symbolic category names for readability */
  72.    lose    = 2
  73.    gTo     = 1
  74.    gFrom   = 2
  75.    gReply  = 3
  76.    gSubject= 4
  77.    gEarly  = 5
  78.    gDating = 6 /* Used for combination test, not in list */
  79.    
  80.    /* load the lose and keep lists */
  81.    list.keep = keepList
  82.    list.lose = loseList
  83.    listfield. = 0
  84.    listpattern. = ''
  85.  
  86.  
  87.  
  88.    
  89.    /****************************************************************/
  90.    /* Embed rules here, or put them in keeplist and loselist files */
  91.  
  92.    /* Let through mails specifically about this program */
  93.    
  94.    CALL keep_rule "Subject: SpamFryer"   
  95.    CALL keep_rule "Subject: Spam Frier" /* Allow various spellings */  
  96.    CALL keep_rule "Subject: Spam Fryer"   
  97.    
  98.    /* These three rules catch a lot of spam */
  99.    
  100.    CALL lose_rule "Subject: *SPAM*"    /* Premarked by mail server */
  101.    CALL lose_rule "To: www."           /* Idiot mailing web server */
  102.    CALL lose_rule "To: undisclosed-recipients" /* A spam indicator */
  103.    
  104.    /* These all signify a HTML mail with no plain text - i.e. spam */
  105.  
  106.    CALL lose_rule "Early-Ref: </"
  107.    CALL lose_rule "Early-Ref: <HTML>"
  108.    CALL lose_rule "Early-Ref: text/html"
  109.    
  110.    /****************************************************************/
  111.    
  112.    
  113.  
  114.    
  115.    DO type = keep TO lose
  116.      IF list.type ~='' THEN DO
  117.        IF ~OPEN(list,list.type,'R') THEN DO
  118.          sigh = list.type 'not found.'
  119.          SAY sigh
  120.          IF logFile ~= '' THEN CALL WRITELN(log, sigh)
  121.        END
  122.        ELSE DO
  123.          IF verbose THEN SAY list.type "rules:"
  124.          iterations = 0
  125.          counter = count.type
  126.          DO WHILE ~EOF(list)
  127.            iterations = iterations + 1
  128.            listerror = 0
  129.            CALL learn_rule READLN(list), type
  130.            IF listerror THEN DO
  131.              sigh = 'ERROR in' list.type 'line' iterations 'skipped.'
  132.              SAY sigh
  133.              IF logFile ~= '' & verbose THEN CALL WRITELN(log, sigh)
  134.            END
  135.          END
  136.          CALL CLOSE(list)
  137.          IF verbose THEN DO
  138.            IF counter = 0 THEN moan = list.type 'not loaded.'
  139.            ELSE moan = list.type 'loaded.'
  140.            SAY moan
  141.            IF logFile ~='' THEN CALL WRITELN(log, moan)
  142.          END
  143.        END
  144.      END
  145.    END
  146.  
  147.  
  148.  
  149.    IF Account.1.1 = '' THEN DO
  150.       sigh = 'You need to configure your mail host and user name into SpamFryer.rexx'
  151.       SAY sigh
  152.       IF logFile ~='' THEN CALL writeln(log, sigh)
  153.       IF OPEN(list,accountFile,'R') THEN DO
  154.         sigh = '(or' accountFile 'if you prefer to keep these in a separate file).' 
  155.         SAY sigh
  156.         IF logFile ~='' THEN CALL writeln(log, sigh)
  157.         CALL CLOSE(list)
  158.       END
  159.       IF logFile ~='' THEN CALL CLOSE(log)
  160.      EXIT 5
  161.    END
  162.  
  163.  
  164.  
  165.    
  166.    idx = 1
  167.    DO WHILE LENGTH(Account.idx.1) > 0
  168.  
  169.       pophost  = Account.idx.1
  170.       username = Account.idx.2
  171.       password = Account.idx.3
  172.       
  173.       IF password = '' THEN DO /* Interactive password support by John 'Kwah' Smith */
  174.         CALL addlib('rexxreqtools.library', 0, -30, 37) /* Implicitly uses reqtools.library */
  175.         CALL addlib(LibName, Priority, Offset, Version)
  176.         IF ~show('L', 'rexxreqtools.library') THEN DO
  177.           moan = '[rexx]reqtools library for password input not found.'
  178.           SAY moan
  179.           IF logFile ~='' THEN DO
  180.             IF verbose THEN CALL writeln(log,moan)
  181.             CALL close(log)
  182.           END
  183.           EXIT 20
  184.         END          
  185.         password = rtgetstring( , ('Enter password for ' || pophost) ,,, 'rtgs_invisible = >> true',)
  186.       END
  187.       
  188.       SAY ''
  189.       SAY 'Checking login ' || username || '.'
  190.  
  191.       netsend = 'TCP:'||pophost||'/pop3'
  192.       CALL open(net,netsend,'A')
  193.       IF result = 0 THEN DO
  194.          moan = 'Login' username || ': -ERR not found.'
  195.          SAY moan
  196.          IF logFile ~='' THEN DO
  197.             IF verbose THEN CALL writeln(log,moan)
  198.             CALL close(log)
  199.          END
  200.          EXIT 10
  201.       END
  202.       instring=READLN(net)
  203.       CALL test_str(instring)
  204.       CALL writeln(net,'USER '||username||'0d'x)
  205.       instring=READLN(net)
  206.       CALL test_str(instring)
  207.       CALL writeln(net,'PASS '||password||'0d'x)
  208.       instring=READLN(net)
  209.       CALL test_str(instring)
  210.       CALL writeln(net,'STAT'||'0d'x)
  211.       instring=READLN(net)
  212.       CALL test_str(instring)
  213.       info = substr(instring,5,length(instring)-5)
  214.       parse var info nmess ' ' noct
  215.       IF nmess = 0 THEN DO
  216.         SAY 'There are no messages waiting.'
  217.       END
  218.       ELSE DO
  219.         moan = 'Messages:' nmess ' Bytes:' noct
  220.         SAY moan
  221.         IF logFile ~= '' & verbose THEN CALL writeln(log, moan)
  222.  
  223.         /* Clear counters */
  224.  
  225.         nlose = 0
  226.       
  227.         IF logFile ~='' THEN DO /* Per-account headings requested by Elwood */
  228.           CALL writeln(log,'')
  229.           CALL writech(log,'Account ' || username || ' on ' pophost ' had ' || nmess)
  230.           IF nmess = 1 THEN CALL writeln(log,'message.')
  231.           ELSE CALL writeln(log,' messages.')
  232.         END 
  233.  
  234.  
  235.  
  236.         /* Process all messages */
  237.         DO message = 1 TO nmess
  238.         
  239.           CALL writeln(net,'TOP '||message||' 12'||'0d'x)
  240.  
  241.           instring = ''
  242.           msgdate = ''
  243.           msgfrom = ''
  244.           msgsubj = ''
  245.           msgrepl = ''
  246.           bad = 0
  247.           good = 0
  248.           dating = 0
  249.           datematch = 0
  250.           digitsift = 0
  251.           field = 0
  252.  
  253.           DO UNTIL (LEFT(instring,2)='.' || '0d'x)
  254.  
  255.              instring=READLN(net)
  256.              IF good & msgdate ~='' & msgfrom ~='' & msgsubj ~='' THEN ITERATE 
  257.              /* Speed up if we know it's not spam */
  258.              /* Speed up if no keeplist: IF bad & ~verbose & msgsubj ~='' THEN ITERATE */
  259.  
  260.              inCaps = UPPER(instring)
  261.              IF LEFT(instring,6)='From: ' THEN DO
  262.                field = gFrom
  263.                msgfrom = instring               
  264.                DO i = 1 TO listfield.keep.gFrom
  265.                  IF POS(listpattern.keep.gFrom.i,inCaps)>0 THEN CALL good_one
  266.                END
  267.                DO i = 1 TO listfield.lose.gFrom
  268.                  IF POS(listpattern.lose.gFrom.i,inCaps)>0 THEN CALL bad_one
  269.                END
  270.              END
  271.  
  272.              IF LEFT(instring,4)='To: ' THEN DO
  273.                IF noDigits THEN DO
  274.                   DO i = 0 to 9 /* Reject mails with any digits in recipient address */
  275.                     IF POS(i,instring)>0 THEN DO
  276.                       bad = 1
  277.                       digitsift = digitsift + 1
  278.                       IF verbose THEN DO
  279.                         SAY 'Lost' message 'for digit in address'
  280.                  END
  281.                     END
  282.                   END
  283.                END
  284.                field = gTo
  285.                DO i = 1 TO listfield.keep.gTo
  286.                  IF POS(listpattern.keep.gTo.i,inCaps)>0 THEN CALL good_one
  287.                END
  288.                DO i = 1 TO listfield.lose.gTo
  289.                  IF POS(listpattern.lose.gTo.i,inCaps)>0 THEN CALL bad_one
  290.                END
  291.              END
  292.  
  293.              IF LEFT(instring,10)='Reply-To: ' THEN DO
  294.                field = gReply
  295.                msgrepl = instring
  296.                DO i = 1 TO listfield.keep.gReply
  297.                  IF POS(listpattern.keep.gReply.i,inCaps)>0 THEN CALL good_one
  298.                END
  299.                DO i = 1 TO listfield.lose.gReply
  300.                  IF POS(listpattern.lose.gReply.i,inCaps)>0 THEN CALL bad_one
  301.                END
  302.              END
  303.  
  304.              IF LEFT(instring,9)='Subject: ' THEN DO
  305.                field = gSubject
  306.                msgsubj = instring
  307.                DO i = 1 TO listfield.keep.gSubject
  308.                  IF POS(listpattern.keep.gSubject.i,inCaps)>0 THEN CALL good_one
  309.                END
  310.                DO i = 1 TO listfield.lose.gSubject
  311.                  IF POS(listpattern.lose.gSubject.i,inCaps)>0 THEN CALL bad_one
  312.                END
  313.              END
  314.  
  315.              IF LEFT(instring,6)='Date: ' THEN msgdate = right(instring,length(instring)-6)
  316.  
  317.              /* Several of the following indicate notorious dating sites */
  318.              IF POS(' DATING ',inCaps)>0 THEN dating = dating + 1;
  319.              IF POS('CREATED BY WOMEN',inCaps)>0 THEN dating = dating + 1;
  320.  
  321.              field = gEarly
  322.              DO i = 1 TO listfield.keep.gEarly
  323.                IF POS(listpattern.keep.gEarly.i,inCaps)>0 THEN CALL good_one
  324.              END
  325.              DO i = 1 TO listfield.lose.gEarly
  326.                IF POS(listpattern.lose.gEarly.i,inCaps)>0 THEN CALL bad_one
  327.              END
  328.  
  329.           END
  330.  
  331.  
  332.  
  333.  
  334.           IF dating>1 THEN DO
  335.             field = gDating /* Require two references to zap a mail */
  336.             CALL bad_one
  337.           END
  338.  
  339.           IF msgfrom = '' THEN msgfrom = msgrepl
  340.           IF (LENGTH(msgfrom) + LENGTH(msgsubj)) = 0 THEN bad = 1 /* © Elwood */         
  341.           IF msgfrom = '' THEN msgfrom = 'From: ???????????'
  342.           IF msgdate = '' THEN msgdate = 'Date: ???????????'
  343.           IF msgsubj = '' THEN msgsubj = 'No subject'
  344.           IF length(msgsubj) > lineMax THEN msgsubj = LEFT(msgsubj,lineMax)
  345.  
  346.           IF verbose THEN DO
  347.             SAY ''
  348.             SAY 'Message '||message||' of' nmess ': '||msgdate
  349.             SAY msgfrom
  350.             SAY msgsubj
  351.           END
  352.  
  353.           IF (good=0) & (bad>0) THEN DO
  354.  
  355.             IF ~verbose THEN SAY "Fry" message "of" nmess msgsubj
  356.  
  357.             IF logFile ~= '' THEN DO
  358.               CALL writeln(log,'Message '||message||': '||msgdate)
  359.               CALL writeln(log,msgfrom)
  360.               CALL writeln(log,msgsubj)
  361.             END
  362.  
  363.             IF bad THEN DO
  364.               CALL writeln(net,'DELE '||message||'0d'x)
  365.               IF verbose THEN DO
  366.                 moan = "$$$$$ Fried"
  367.                 SAY moan
  368.                 IF logFile ~= '' THEN CALL writeln(log,moan)
  369.               END
  370.               instring=READLN(net)
  371.               nlose = nlose + 1
  372.               CALL test_str(instring)
  373.               ITERATE message
  374.             END
  375.           END
  376.  
  377.           IF ~verbose THEN ITERATE message
  378.          
  379.           moan = "----- Left on server"
  380.           SAY moan
  381.           IF logFile ~='' & verbose THEN DO
  382.             CALL writeln(log,'Message '||message||': '||msgdate)
  383.             CALL writeln(log,msgfrom)
  384.             CALL writeln(log,msgsubj)
  385.             CALL writeln(log,moan)
  386.           END
  387.        END /* Loop for all messages */
  388.  
  389.  
  390.  
  391.  
  392.        IF nmess ~= 0 THEN DO
  393.          SAY ''
  394.          other = nmess-nlose
  395.          moan = nlose "of" nmess "mail"
  396.          IF nmess ~=1 THEN moan = moan || "s"
  397.          moan = "Fried" moan "for account" username "on" pophost
  398.          SAY moan
  399.          IF logFile ~='' & verbose THEN CALL writeln(log,moan)
  400.        END
  401.  
  402.      END /* Some messages */
  403.      CALL writeln(net,'QUIT'||'0d'x)
  404.      CALL close(net)
  405.      idx = idx + 1
  406.    END  /* do while any accounts unchecked */
  407.    IF logFile ~='' THEN CALL close(log)
  408.    EXIT 0
  409.  
  410.  
  411.  
  412.  
  413. /* good_one keeps track of emails identified by the keeplist    */
  414.  
  415. good_one:
  416.         IF field = 0 THEN RETURN 0 /* Unlikely error */
  417.     good = 1
  418.     IF verbose THEN DO
  419.       SAY 'Kept' messsage 'for' listpattern.keep.field.i
  420.           listpattern.keep.field.i.1 = listpattern.keep.field.i.1 + 1
  421.         END
  422.     return 1
  423.  
  424.  
  425. /* bad_one keeps track of emails identified by the loselist     */
  426.  
  427. bad_one:
  428.         IF field = 0 THEN RETURN 0 /* Error */
  429.     bad = 1
  430.     IF verbose THEN DO
  431.         IF field = gDating THEN DO
  432.           SAY 'Lost' message 'for dating reference'
  433.           datematch = datematch + 1
  434.         END
  435.       ELSE DO
  436.         SAY 'Lost' message 'for' listpattern.lose.field.i
  437.             listpattern.lose.field.i.1 = listpattern.lose.field.i.1 + 1
  438.           END
  439.     END
  440.     return 1
  441.  
  442.  
  443.  
  444.  
  445. /* keep_rule teaches the program a way to identify good emails  */
  446.  
  447. keep_rule:
  448.  
  449.    listerror = 0
  450.    CALL learn_rule arg(1), keep
  451.    IF listerror THEN DO
  452.      sigh = 'ERROR: Rule "' arg(1) '" in SpamFryer.rexx skipped.'
  453.      SAY sigh
  454.      IF logFile ~= '' & verbose THEN CALL WRITELN(log, sigh)
  455.    END
  456.    return 1
  457.  
  458.  
  459. /*  lose_rule teaches the program a way to identify bad emails  */
  460.  
  461. lose_rule:
  462.  
  463.    listerror = 0
  464.    CALL learn_rule arg(1), lose 
  465.    IF listerror THEN DO
  466.      sigh = 'ERROR: Rule "' arg(1) '" in SpamFryer.rexx skipped.'
  467.      SAY sigh
  468.      IF logFile ~= '' & verbose THEN CALL WRITELN(log, sigh)
  469.    END
  470.    return 1
  471.  
  472.  
  473. /*  learn_rule teaches the program a way to categorise emails.  */
  474. /*  The first argument is the rule. The second is the category  */
  475. /*  listerror is set if the rule is not correctly formatted.    */
  476.  
  477. learn_rule:
  478.  
  479.    instring = UPPER(arg(1))
  480.    kind = arg(2)
  481.    PARSE VAR instring word1 word2 .   /* get the first and the second word in the line */
  482.    IF word1 = "//" THEN return 1      /* Allow C++ comments in the configuration lines */
  483.    IF word2 = '' THEN DO
  484.      IF word1 ~= '' THEN listerror = 1   /* Error if there is only one word in the string */
  485.    END
  486.    ELSE DO
  487.      field = 0
  488.      IF RIGHT(word1,1) ~= ':' THEN word1 = word1 || ':'
  489.      IF word1 = 'TO:' THEN field = gTo
  490.      IF word1 = 'FROM:' THEN field = gFrom
  491.      IF word1 = 'REPLY-TO:' THEN field = gReply
  492.      IF word1 = 'REPLYTO:' THEN field = gReply
  493.      IF word1 = 'SUBJECT:' THEN field = gSubject
  494.      IF word1 = 'EARLY-REF:' THEN field = gEarly
  495.      IF word1 = 'EARLYREF:' THEN field = gEarly
  496.      IF field = 0 THEN listerror = 1   /* Error, unexpected first word on line */
  497.      ELSE DO
  498.        instring = STRIP(instring,'L')
  499.        instring = RIGHT(instring,LENGTH(instring)-POS(' ',instring))   /* cut off field parameter, leaving only the search pattern */
  500.        instring = STRIP(instring)
  501.        IF RIGHT(instring,1) = "'" & LEFT(instring,1)="'" THEN instring = STRIP(instring,'B',"'")
  502.        IF RIGHT(instring,1) = '"' & LEFT(instring,1)='"' THEN instring = STRIP(instring,'B','"')       
  503.        listfield.kind.field = listfield.kind.field + 1   /* count entries in each field parameter for each list */
  504.        counter = listfield.kind.field
  505.        listpattern.kind.field.counter = instring   /* Store search pattern */
  506.        listpattern.kind.field.counter.1 = 0    /* No matches yet */
  507.        IF verbose THEN DO
  508.          IF kind = keep THEN sigh = 'KEEP'
  509.          ELSE sigh = 'LOSE'
  510.          SAY sigh counter word1 instring
  511.        END
  512.      END
  513.    END
  514.    return 1
  515.  
  516.  
  517. /*  check_accounts reads POP3 account login details from a file */
  518.  
  519. Check_Accounts:
  520.  
  521.   IF OPEN(list,accountFile,'R') THEN DO
  522.     accounts = 1
  523.     DO WHILE Account.accounts.1 ~=''
  524.       accounts = accounts + 1 /* Skip acounts defined in this file */
  525.     END
  526.  
  527.     userSet = ''
  528.     hostSet = ''
  529.     passSet = ''
  530.     
  531.     counter = count.type
  532.     DO WHILE ~EOF(list)
  533.       instring = READLN(list)
  534.       PARSE VAR instring word1 word2 .   /* get the first and the second word in the line */
  535.       IF word1 ~= "//" & word1 ~= '' THEN DO
  536.         word1 = UPPER(word1)
  537.         field = 0
  538.         IF RIGHT(word1,1) ~= ':' THEN word1 = word1 || ':'
  539.         IF word1 = 'USERNAME:' THEN field = 2
  540.         IF word1 = 'PASSWORD:' THEN field = 3
  541.         IF word1 = 'HOSTNAME:' THEN field = 1
  542.         IF field = 0 THEN DO
  543.           moan = 'ERROR: Unrecognised item' word1 'in' accountFile
  544.           say moan
  545.           IF logFile ~='' THEN CALL WRITELN(log, moan)   
  546.         END
  547.  
  548.        IF RIGHT(word2,1) = "'" & LEFT(word2,1)="'" THEN word2 = STRIP(word2,'B',"'")
  549.        IF RIGHT(word2,1) = '"' & LEFT(word2,1)='"' THEN word2 = STRIP(word2,'B','"')       
  550.         
  551.         /* Make sure each host has one associated username and one or zero passwords */
  552.         ELSE DO
  553.           IF field = 1 THEN DO
  554.             IF hostSet ~= '' THEN DO
  555.               IF userSet = '' THEN DO
  556.                 sigh = 'ERROR: No user name configured for host' hostSet 'in' AccountFile
  557.                 SAY sigh
  558.                 IF logFile ~= '' THEN CALL WRITELN(log, sigh)          
  559.               END  
  560.               ELSE DO 
  561.                 accounts = accounts + 1
  562.                 passSet = ''            
  563.                 userSet = ''            
  564.               END        
  565.             END         
  566.             hostSet = word2
  567.           END
  568.           
  569.           IF field = 2 THEN DO
  570.             IF userSet ~= '' THEN DO
  571.               IF hostSet = '' THEN DO
  572.                 sigh = 'ERROR: No host configured for user' userSet 'in' AccountFile
  573.                 SAY sigh
  574.                 IF logFile ~= '' THEN CALL WRITELN(log, sigh)
  575.               END            
  576.               ELSE DO 
  577.                 accounts = accounts + 1
  578.                 hostSet = ''
  579.                 passSet = ''            
  580.               END        
  581.             END         
  582.             userSet = word2
  583.           END
  584.           
  585.           IF field = 3 THEN DO
  586.             IF passSet ~= '' THEN DO
  587.               sigh = 'ERROR: Duplicate passwords' passSet 'and' word2 'in' AccountFile
  588.               SAY sigh
  589.               IF logFile ~= '' THEN CALL WRITELN(log, sigh)            
  590.             END
  591.             passSet = word2                    
  592.           END
  593.           
  594.           Account.accounts.field = word2          
  595.         END /* Valid field */          
  596.  
  597.       END /* Not comment or blank line */
  598.     END /* WHILE list */
  599.     CALL CLOSE(list)
  600.     
  601.     IF userSet = '' & hostSet = '' THEN DO
  602.       IF passSet ~= '' THEN DO
  603.         sigh = 'ERROR: Password' passSet 'in' AccountFile 'needs a host and user name'
  604.         SAY sigh
  605.         IF logFile ~= '' THEN CALL WRITELN(log, sigh)            
  606.       END   
  607.     END
  608.     ELSE DO
  609.       IF hostSet = '' THEN DO
  610.         sigh = 'ERROR: No host configured for user' userSet 'in' AccountFile
  611.         SAY sigh
  612.         IF logFile ~= '' THEN CALL WRITELN(log, sigh)            
  613.       END    
  614.       IF userSet = '' THEN DO
  615.         sigh = 'ERROR: No user name configured for host' hostSet 'in' AccountFile
  616.         SAY sigh
  617.         IF logFile ~= '' THEN CALL WRITELN(log, sigh)            
  618.       END
  619.     END 
  620.   END
  621.   
  622.   IF verbose THEN DO
  623.     accounts = 1
  624.     DO WHILE Account.accounts.1 ~=''
  625.       sigh = 'Host' Account.accounts.1 'User' Account.accounts.2 'Password' Account.accounts.3
  626.       SAY sigh  
  627.       IF verbose THEN CALL writeln(log, sigh)
  628.       accounts = accounts + 1
  629.     END
  630.   END
  631.  
  632.   return 1
  633.  
  634.    
  635.  
  636.  
  637. /* test_str tests the response from the POP server: +OK or -ERR */
  638.  
  639. test_str:
  640.  
  641.    test = arg(1)
  642.    /* SAY 'Server Response:' test */
  643.    IF POS('+OK',test)~=0 THEN
  644.       return 1
  645.    ELSE
  646.       moan = 'Login' username || ':' test
  647.       SAY moan
  648.       IF logFile ~='' THEN DO
  649.          IF verbose THEN CALL writeln(log, moan)
  650.          CALL close(log)
  651.       END
  652.       CALL writeln(net,'QUIT'||'0d'x)
  653.       CALL close(net)
  654.       EXIT 10
  655.